//#preprocessor

/*
 * File: CMapFormat4.java
 * Version: 1.3
 * Initial Creation: May 18, 2010 9:03:58 PM
 *
 * Copyright 2004 Sun Microsystems, Inc., 4150 Network Circle,
 * Santa Clara, California 95054, U.S.A. All rights reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */
package com.sun.pdfview.font.ttf;

//#ifndef BlackBerrySDK4.5.0 | BlackBerrySDK4.6.0 | BlackBerrySDK4.6.1 | BlackBerrySDK4.7.0 | BlackBerrySDK4.7.1
import java.nio.ByteBuffer;
//#else
import com.sun.pdfview.helper.nio.ByteBuffer;
//#endif
import java.util.Enumeration;
import java.util.Hashtable;

import net.rim.device.api.util.MathUtilities;

import com.sun.pdfview.helper.PDFUtil;

/**
*
* @author  jkaplan
*/
public class CMapFormat4 extends CMap
{
	/**
     * The segments and associated data can be a char[] or an Integer
     */
    public Hashtable segments;
    
    /** Creates a new instance of CMapFormat0 */
    protected CMapFormat4(short language)
    {
        super((short)4, language);
        
        segments = PDFUtil.synchronizedTable(new Hashtable());
        
        char[] map = new char[1];
        map[0] = (char)0;
        addSegment((short)0xffff, (short) 0xffff, map);
    }
    
    /**
     * Add a segment with a map 
     */
    public void addSegment(short startCode, short endCode, char[] map)
    {
        if (map.length != (endCode - startCode) + 1)
        {
            throw new IllegalArgumentException(com.sun.pdfview.ResourceManager.getResource(com.sun.pdfview.ResourceManager.LOCALIZATION).getString(com.sun.pdfview.i18n.ResourcesResource.FONT_TTF_CMAP4_WRONG_ENT_COUNT));
        }
        
        Segment s = new Segment(startCode, endCode, true);
        // make sure we remove any old entries
        segments.remove(s);
        segments.put(s, map);
    }
    
    /**
     * Add a segment with an idDelta
     */
    public void addSegment(short startCode, short endCode, short idDelta)
    {
        Segment s = new Segment(startCode, endCode, false);
        // make sure we remove any old entries
        segments.remove(s);
        segments.put(s, new Integer(idDelta));
    }
    
    /**
     * Remove a segment
     */
    public void removeSegment(short startCode, short endCode)
    {
        Segment s = new Segment(startCode, endCode, true);
        segments.remove(s);
    }
    
    /**
     * Get the length of this table
     */
    public short getLength()
    {
        // start with the size of the fixed header
        short size = 16;
        
        // add the size of each segment header
        size += segments.size() * 8;
        
        // add the total number of mappings times the size of a mapping
        for (Enumeration i = segments.keys(); i.hasMoreElements();)
        {
            Segment s = (Segment)i.nextElement();
            
            // see if there's a map
            if (s.hasMap)
            {
                // if there is, add its size
                char[] map = (char[]) segments.get(s);
                size += map.length * 2;
            }
        }
        
        return size;
    }
    
    /** 
     * Cannot map from a byte
     */
    public byte map(byte src)
    {
        char c = map((char)src);
        if (c < Byte.MIN_VALUE || c > Byte.MAX_VALUE)
        {
            // out of range
            return 0;
        }
        
        return (byte) c;
    }
    
    /**
     * Map from char
     */
    public char map(char src)
    {
        // find first segment with endcode > src
        for (Enumeration i = segments.keys(); i.hasMoreElements();)
        {
            Segment s = (Segment)i.nextElement();
            
            if (s.endCode >= src)
            {
                // are we within range?
                if (s.startCode <= src)
                {
                    if (s.hasMap)
                    {
                        // return the index of this character in 
                        // the segment's map
                        char[] map = (char[])segments.get(s);
                        return map[src - s.startCode];
                    }
                    else
                    {
                        // return the character code + idDelta
                        Integer idDelta = (Integer) segments.get(s);
                        return (char)(src + idDelta.intValue());
                    }
                }
                else
                {
                    // undefined character
                    return (char) 0;
                }
            }
        }
        
        // shouldn't get here!
        return (char)0;
    }
    
    /**
     * Get the src code which maps to the given glyphID
     */
    public char reverseMap(short glyphID)
    {
        // look at each segment
        for (Enumeration i = segments.keys(); i.hasMoreElements();)
        {
            Segment s = (Segment)i.nextElement();
            
            // see if we have a map or a delta
            if (s.hasMap)
            {
                char[] map = (char[])segments.get(s);
                
                // if we have a map, we have to iterate through it
                int len = map.length;
                for (int c = 0; c < len; c++)
                {
                    if (map[c] == glyphID)
                    {
                        return (char)(s.startCode + c);
                    }
                }
            }
            else
            {
                Integer idDelta = (Integer)segments.get(s);
                
                // we can do the math to see if we're in range
                int start = s.startCode + idDelta.intValue();
                int end = s.endCode + idDelta.intValue();
                
                if (glyphID >= start && glyphID <= end)
                {
                    // we're in the range
                    return (char)(glyphID - idDelta.intValue());
                }
            }
        }
        
        // not found!
        return (char)0;
    }
    
    /**
     * Get the data in this map as a ByteBuffer
     */
    public void setData(int length, ByteBuffer data)
    {
        // read the table size values
        short segCount = (short)(data.getShort() / 2);
        short searchRange = data.getShort();
        short entrySelector = data.getShort();
        short rangeShift = data.getShort();
        
        // create arrays to store segment info
        short[] endCodes = new short[segCount];
        short[] startCodes = new short[segCount];
        short[] idDeltas = new short[segCount];
        short[] idRangeOffsets = new short[segCount];
        
        // the start of the glyph array
        int glyphArrayPos = 16 + (8 * segCount);
        
        // read the endCodes
        for (int i = 0; i < segCount; i++)
        {
           endCodes[i] = data.getShort();
        }
        
        // read the pad
        data.getShort();
        
        // read the start codes
        for (int i = 0; i < segCount; i++)
        {
            startCodes[i] = data.getShort();
        }
        
        // read the idDeltas
        for (int i = 0; i < segCount; i++)
        {
            idDeltas[i] = data.getShort();
        }
        
        // read the id range offsets
        for (int i = 0; i < segCount; i++)
        {
            idRangeOffsets[i] = data.getShort();
            
            // calculate the actual offset
            if (idRangeOffsets[i] <= 0)
            {
                // the easy way
                addSegment(startCodes[i], endCodes[i], idDeltas[i]);
            }
            else
            {
                // find the start of the data segment
                int offset = (data.position() - 2) + idRangeOffsets[i];
                
                // get the number of entries in the map
                int size = (endCodes[i] - startCodes[i]) + 1;
                
                // allocate the actual map
                char[] map = new char[size];
                
                // remember our offset
                int pos = data.position();
                
                // read the mappings    
                for (int c = 0; c < size; c++)
                {
                    data.position(offset + (c * 2));
                    map[c] = (char)data.getShort();
                }
                
                // reset the position
                data.position(pos);
                
                addSegment(startCodes[i], endCodes[i], map);
            }
        }       
    }
    
    /** 
     * Get the data in the map as a byte buffer
     */
    public ByteBuffer getData()
    {
        ByteBuffer buf = ByteBuffer.allocateDirect(getLength());
        
        // write the header
        buf.putShort(getFormat());
        buf.putShort((short) getLength());
        buf.putShort(getLanguage());
        
        // write the various values
        buf.putShort((short)(getSegmentCount() * 2));
        buf.putShort(getSearchRange());
        buf.putShort(getEntrySelector());
        buf.putShort(getRangeShift());
        
        // write the endCodes
        for (Enumeration i = segments.keys(); i.hasMoreElements();)
        {
            Segment s = (Segment)i.nextElement();
            buf.putShort((short) s.endCode);
        }
        
        // write the pad
        buf.putShort((short)0);
        
        // write the startCodes
        for (Enumeration i = segments.keys(); i.hasMoreElements();)
        {
            Segment s = (Segment)i.nextElement();
            buf.putShort((short)s.startCode);
        }
        
        // write the idDeltas for segments using deltas
        for (Enumeration i = segments.keys(); i.hasMoreElements();)
        {
            Segment s = (Segment)i.nextElement();
            
            if (!s.hasMap)
            {
                Integer idDelta = (Integer)segments.get(s);
                buf.putShort(idDelta.shortValue());
            }
            else
            {
                buf.putShort((short)0);
            }
        }
        
        // the start of the glyph array
        int glyphArrayOffset = 16 + (8 * getSegmentCount());
        
        // write the idRangeOffsets and maps for segments using maps
        for (Enumeration i = segments.keys(); i.hasMoreElements();)
        {
            Segment s = (Segment)i.nextElement();
            
            if (s.hasMap)
            {
                // first set the offset, which is the number of bytes from the
                // current position to the current offset
                buf.putShort((short)(glyphArrayOffset - buf.position()));
                
                // remember the current position
                int pos = buf.position();
                
                // move the position to the offset
                buf.position(glyphArrayOffset);
                
                // now write the map
                char[] map = (char[])segments.get(s);
                for (int c = 0; c < map.length; c++)
                {
                	buf.putShort((short)map[c]);
                }
                
                // reset the data pointer
                buf.position(pos);
                
                // update the offset
                glyphArrayOffset += map.length * 2;
            }
            else
            {
                buf.putShort((short)0);
            }
        }
        
        // make sure we are at the end of the buffer before we flip
        buf.position(glyphArrayOffset);
        
        // reset the data pointer
        buf.flip();
        
        return buf;
    }
    
    /**
     * Get the segment count
     */
    public short getSegmentCount()
    {
        return (short)segments.size();
    }
    
    /**
     * Get the search range
     */
    public short getSearchRange()
    {
        double pow = Math.floor(MathUtilities.log(getSegmentCount()) / MathUtilities.log(2));
//#ifndef BlackBerrySDK4.5.0
        double pow2 = MathUtilities.pow(2, pow);
//#else
        double pow2 = littlecms.internal.helper.Utility.pow(2, pow);
//#endif
        
        return (short) (2 * pow2);
    }
    
    /**
     * Get the entry selector
     */
    public short getEntrySelector()
    {
        int sr2 = getSearchRange() / 2;
        return (short) (MathUtilities.log(sr2) / MathUtilities.log(2));
    }
    
    /**
     * Get the rangeShift()
     */
    public short getRangeShift()
    {
        return (short)((2 * getSegmentCount()) - getSearchRange());
    }
    
    /** Get a pretty string */
    public String toString()
    {
        StringBuffer buf = new StringBuffer();
        String indent = "        ";
        
        buf.append(super.toString());
        buf.append(indent + "SegmentCount : " + getSegmentCount() + "\n");
        buf.append(indent + "SearchRange  : " + getSearchRange() + "\n");
        buf.append(indent + "EntrySelector: " + getEntrySelector() + "\n");
        buf.append(indent + "RangeShift   : " + getRangeShift() + "\n");
        
        for (Enumeration i = segments.keys(); i.hasMoreElements();)
        {
            Segment s = (Segment)i.nextElement();
            
            buf.append(indent);
            buf.append("Segment: " + Integer.toHexString(s.startCode));
            buf.append("-" + Integer.toHexString(s.endCode) + " ");
            buf.append("hasMap: " + s.hasMap + " ");
            
            if (!s.hasMap)
            {
                buf.append("delta: " + segments.get(s));
            }
            
            buf.append("\n");
        }
        
        return buf.toString();
    }
    
    class Segment
//#ifndef BlackBerrySDK4.5.0 | BlackBerrySDK4.6.0 | BlackBerrySDK4.6.1 | BlackBerrySDK4.7.0 | BlackBerrySDK4.7.1
    	implements Comparable
//#endif
    {
        /** the end code (highest code in this segment) */
        int endCode;
        
        /** the start code (lowest code in this segment) */
        int startCode;
        
        /** whether it is a map or a delta */
        boolean hasMap;
        
        /** Create a new segment */
        public Segment(short startCode, short endCode, boolean hasMap)
        {
            // convert from unsigned short
            this.endCode   = (0xffff & endCode);
            this.startCode = (0xffff & startCode);
            
            this.hasMap = hasMap;
        }
        
        /** Equals based on compareTo (only compares endCode) */
        public boolean equals(Object o)
        {
            return (compareTo(o) == 0);
        }
        
        /** Segments sort by increasing endCode */
        public int compareTo(Object o)
        {
            if (!(o instanceof Segment))
            {
                return -1;
            }
            
            Segment s = (Segment)o;
            
            // if regions overlap at all, declare the segments equal,
            // to avoid overlap in the segment list
            if (((s.endCode >= startCode) && (s.endCode <= endCode)) || ((s.startCode >= startCode) && (s.startCode <= endCode)))
            {
                return 0;
            }
            if (endCode > s.endCode)
            {
                return 1;
            }
            else if (endCode < s.endCode)
            {
                return -1;
            }
            else
            {
                return 0;
            }
        }
    }
}
